package lookie.swing.tree.navigation;

import java.util.ArrayList;
import java.util.List;

import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

public abstract class Navigator implements TreeExpansionListener {

	private JTree tree;

	private DefaultTreeModel rootModel;

	private DefaultTreeModel currentModel;

	private TreeNode root;

	private TreeNode currentRoot;

	private List<TreeNode> expandedNodes = new ArrayList<TreeNode>();

	private boolean monitorExpansion = true;

	public Navigator(JTree tree, TreeNode root) {

		this.tree = tree;
		this.root = root;
	}
	
	public void init() {
		
		currentRoot = root;
		rootModel = newTreeModel(root);

		tree.addTreeExpansionListener(this);
		tree.setModel(rootModel);
	}

	public void goTo(TreeNode node) {
		currentModel = newTreeModel(node);
		currentRoot = node;
		tree.setModel(currentModel);
		restoreExpandedNodes();
	}

	public void goUp() {
		TreeNode node = currentRoot.getParent();
		goTo(node);
	}

	public void goHome() {
		goTo(root);
	}

	public boolean canGoUp() {
		return currentRoot != root;
	}

	protected abstract DefaultTreeModel newTreeModel(TreeNode root);

	private void restoreExpandedNodes() {
		monitorExpansion = false;
		for (TreeNode node : expandedNodes) {
			if (isAncestor(currentRoot, node)) {
				tree.expandPath(new TreePath(currentModel.getPathToRoot(node)));
			}
		}
		monitorExpansion = true;
	}

	private boolean isAncestor(TreeNode ancestor, TreeNode child) {
		if (child == null) {
			return false;
		}
		if (child.getParent() == null) {
			return false;
		}
		if (child.getParent().equals(ancestor)) {
			return true;
		}
		return isAncestor(ancestor, child.getParent());
	}

	public void treeCollapsed(TreeExpansionEvent event) {
		if (monitorExpansion) {
			TreeNode node = (TreeNode) event.getPath().getLastPathComponent();
			if (expandedNodes.contains(node)) {
				expandedNodes.remove(node);
			}
		}
	}

	public void treeExpanded(TreeExpansionEvent event) {
		if (monitorExpansion) {
			TreeNode node = (TreeNode) event.getPath().getLastPathComponent();
			if (!expandedNodes.contains(node)) {
				expandedNodes.add(node);
			}
		}
	}

}
